package com.whq.thrift.api.gateway.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;

import com.alibaba.fastjson.JSON;
import com.whq.common.InetAddressUtil;
import com.whq.common.StringUtil;
import com.whq.common.UUIDUtil;
import com.whq.common.http.HttpClientUtil;
import com.whq.thrift.api.common.constants.CommonConfigConstants;
import com.whq.thrift.api.common.constants.CommonErrorCodeConstants;
import com.whq.thrift.api.common.httpsignature.SignatureUtil;
import com.whq.thrift.api.common.kafka.KafkaStringProducer;
import com.whq.thrift.api.common.spring.ReloadableConfManager;
import com.whq.thrift.api.common.thriftclient.AbstractApiJsonCallback;
import com.whq.thrift.api.common.thriftclient.InvokeThriftStatus;
import com.whq.thrift.api.common.thriftserver.ResponseBase;
import com.whq.thrift.api.common.thriftserver.ResponseDataMapBase;
import com.whq.thrift.api.gateway.constants.GatewayErrorCodeConstants;
import com.whq.thrift.api.gateway.constants.GatewayVerifyErrorCodeConstants;
import com.whq.thrift.api.gateway.db.MethodInfo;
import com.whq.thrift.api.gateway.service.VerifyDbService;

/**
 * Gateway Filter
 * 
 * @author wanghuaiqiang
 * 
 */
public class GatewayFilter implements Filter {
	private static final Logger LOGGER = LoggerFactory.getLogger(GatewayFilter.class);
	private static final Logger LOGGER_HTTP_REQUEST_RESPONSE = LoggerFactory.getLogger("logger_http_request_response");
	private static final int DEFAULT_TIMEOUT_MS = 50;
	private static final int DEFAULT_MAX_RUN_COUNT_PER_SECOND = 10;

	private static final Map<String, Integer> MAP_METHOD_RUN_COUNT = new ConcurrentHashMap<String, Integer>(0);
	private static final Map<String, Long> MAP_METHOD_LAST_RUN_TIMESTAMP = new ConcurrentHashMap<String, Long>(0);

	private static final long CHK_RUN_COUNT_INTERVAL_MS = 1000l;

	private static int runningThread = 0;

	private WebApplicationContext ctx = null;

	public void init(FilterConfig filterConfig) throws ServletException {
		ServletContext servletContext = filterConfig.getServletContext();
		Object ob = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
		ctx = (WebApplicationContext) ob;
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		runningThread++;
		long statTimestamp = System.currentTimeMillis();

		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;

		GatewayIceClientHelper gatewayIceClientHelper = (GatewayIceClientHelper) ctx.getBean("gatewayIceClientHelper");
		ReloadableConfManager reloadableConfManager = (ReloadableConfManager) ctx.getBean("reloadableConfManager");
		VerifyDbService verifyDbService = (VerifyDbService) ctx.getBean("verifyDbService");

		// 获取所有参数
		Map<String, String> mapParams = new HashMap<String, String>(0);
		@SuppressWarnings("unchecked")
		Enumeration<String> paramNames = request.getParameterNames();
		while (paramNames.hasMoreElements()) {
			String paramName = (String) paramNames.nextElement();
			String[] paramValues = request.getParameterValues(paramName);
			if (paramValues.length == 1) {
				String paramValue = paramValues[0];
				mapParams.put(paramName, paramValue);
			}
		}

		Map<String, String> ctx = new HashMap<String, String>(0);

		// 判断是否带 fn_callback
		String fnCallback = mapParams.get(CommonConfigConstants.THRIFT_PARAM_JSONP_CALLBACK_NAME);

		// 处理映射
		String uri = req.getRequestURI();
		String mappingMethod = gatewayIceClientHelper.getMapParamMethodByUrl(uri);
		if (StringUtil.isNotBlank(mappingMethod)) {
			mappingMethod = mappingMethod.trim();
			mapParams.put(CommonConfigConstants.THRIFT_PARAM_METHOD, mappingMethod);
		}

		// ice 上下文
		ctx.put(CommonConfigConstants.THRIFT_CTX_FLOW_ID, UUIDUtil.generateUUID());
		ctx.put(CommonConfigConstants.THRIFT_CTX_REMOTE_ADDRESS, HttpClientUtil.getRealIpAddr(req));
		ctx.put(CommonConfigConstants.THRIFT_CTX_SEQ, "gateway");

		// 判断是否存在method参数
		String method = mapParams.get(CommonConfigConstants.THRIFT_PARAM_METHOD);
		if (StringUtil.isBlank(method)) {
			ResponseBase responseJson = new ResponseBase(CommonErrorCodeConstants.ERR_CODE_LACK_PARAM, "缺少参数：method");
			outputJsonResponse(resp, JSON.toJSONString(responseJson), fnCallback, statTimestamp, ctx, mapParams, reloadableConfManager,
					new ResponseDataMapBase(), method);
			return;
		}

		// 如果方法不存在
		MethodInfo methodInfo = verifyDbService.getMethodInfo(method);
		if (methodInfo == null) {
			ResponseBase responseJson = new ResponseDataMapBase(GatewayVerifyErrorCodeConstants.ERR_CODE_METHDO_NOT_EXIST_OR_INVALID,
					GatewayVerifyErrorCodeConstants.MAP_ERR_CODE_MSG.get(GatewayVerifyErrorCodeConstants.ERR_CODE_METHDO_NOT_EXIST_OR_INVALID));
			outputJsonResponse(resp, JSON.toJSONString(responseJson), fnCallback, statTimestamp, ctx, mapParams, reloadableConfManager,
					new ResponseDataMapBase(), method);
			return;
		}

		// 判断请求是否太频繁
		Long lastRunTimestamp = MAP_METHOD_LAST_RUN_TIMESTAMP.get(method);
		if (lastRunTimestamp == null) {
			lastRunTimestamp = 0l;
		}
		long diff = System.currentTimeMillis() - lastRunTimestamp;
		if (diff > CHK_RUN_COUNT_INTERVAL_MS) {
			MAP_METHOD_RUN_COUNT.put(method, 0);
			MAP_METHOD_LAST_RUN_TIMESTAMP.put(method, System.currentTimeMillis());
		} else {
			Integer runCount = MAP_METHOD_RUN_COUNT.get(method);
			if (runCount == null) {
				runCount = 0;
			}
			runCount++;
			MAP_METHOD_RUN_COUNT.put(method, runCount);

			long maxRunCountPreSecond = methodInfo.getMaxRunCountPreSecond();
			if (maxRunCountPreSecond <= 0) {
				maxRunCountPreSecond = DEFAULT_MAX_RUN_COUNT_PER_SECOND;
			}

			if (runCount >= maxRunCountPreSecond) {
				ResponseBase responseJson = new ResponseDataMapBase(GatewayErrorCodeConstants.ERR_CODE_EXECUTE_TOO_OFTEN,
						GatewayErrorCodeConstants.MAP_ERR_CODE_MSG.get(GatewayErrorCodeConstants.ERR_CODE_EXECUTE_TOO_OFTEN));
				outputJsonResponse(resp, JSON.toJSONString(responseJson), fnCallback, statTimestamp, ctx, mapParams, reloadableConfManager,
						new ResponseDataMapBase(), method);
				return;
			}
		}

		// 调用verify
		ResponseDataMapBase iceVerifyResponseObj = doVerify(verifyDbService, method, mapParams, ctx, gatewayIceClientHelper);
		if (iceVerifyResponseObj == null) {
			ResponseDataMapBase responseJson = new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_PARSE_RESPONSE_ERROR, "verify结果为空");
			outputJsonResponse(resp, JSON.toJSONString(responseJson), fnCallback, statTimestamp, ctx, mapParams, reloadableConfManager,
					new ResponseDataMapBase(), method);
			return;
		}
		String iceVerifyResponseStr = JSON.toJSONString(iceVerifyResponseObj);
		if (iceVerifyResponseObj.getReturnCode() != CommonErrorCodeConstants.ERR_CODE_SUCCESS) {
			outputJsonResponse(resp, iceVerifyResponseStr, fnCallback, statTimestamp, ctx, mapParams, reloadableConfManager, iceVerifyResponseObj, method);
			return;
		}

		ctx.put(CommonConfigConstants.PARAM_TXZ_TICKET_VERIFY_RESULT, iceVerifyResponseStr);

		// 调用业务服务
		// 设置超时
		int executeTimeoutMs = methodInfo.getTimeoutMs();
		if (executeTimeoutMs <= 0) {
			executeTimeoutMs = DEFAULT_TIMEOUT_MS;
		}
		mapParams.remove(CommonConfigConstants.THRIFT_PARAM_JSONP_CALLBACK_NAME);

		String iceBusinessIdentity = methodInfo.getMethodModuleId();
		if (StringUtil.isBlank(iceBusinessIdentity)) {
			List<String> identityMethodList = StringUtil.split2list(method, "\\.");
			iceBusinessIdentity = identityMethodList.get(0);
		}

		String iceBusinessResponseStr = invokeThriftService(gatewayIceClientHelper, iceBusinessIdentity, mapParams, ctx, executeTimeoutMs);

		outputJsonResponse(resp, iceBusinessResponseStr, fnCallback, statTimestamp, ctx, mapParams, reloadableConfManager, iceVerifyResponseObj, method);
		return;
	}

	public void destroy() {

	}

	private void outputJsonResponse(HttpServletResponse response, String json, String fnCallback, long statTimestamp, Map<String, String> ctx,
			Map<String, String> mapParams, ReloadableConfManager reloadableConfManager, ResponseDataMapBase iceVerifyResponseObj, String method) {
		String outputString = (StringUtil.isNotBlank(fnCallback)) ? fnCallback + "(" + json + ")" : json;

		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = null;
		try {
			out = response.getWriter();
			out.print(outputString);
			out.flush();
		} catch (Exception e) {
			LOGGER.error("output response: " + outputString + " error:", e);
		} finally {
			if (out != null) {
				out.close();
			}
		}
		long timeConsumeMs = System.currentTimeMillis() - statTimestamp;
		LOGGER_HTTP_REQUEST_RESPONSE.info("request[" + chkMapDataLength(mapParams) + "], ctx[" + chkMapDataLength(ctx) + "], response[" + json + "], thread["
				+ runningThread + "], consume " + timeConsumeMs + " ms");
		runningThread--;

		try {
			// 发送kafka消息
			String msgTopic = reloadableConfManager.getPropertyValue("kafka.topic.request.log", null, "");
			KafkaRequestLog kafkaRequestLog = new KafkaRequestLog();
			kafkaRequestLog.setApi(method);
			kafkaRequestLog.setFlowId(ctx.get(CommonConfigConstants.THRIFT_CTX_FLOW_ID));
			kafkaRequestLog.setHost(InetAddressUtil.getLocalIp());
			kafkaRequestLog.setParams(chkMapDataLength(mapParams));
			kafkaRequestLog.setRemoteAddr(ctx.get(CommonConfigConstants.THRIFT_CTX_REMOTE_ADDRESS));
			kafkaRequestLog.setTime(System.currentTimeMillis() / 1000l);
			kafkaRequestLog.setVerifyResult(iceVerifyResponseObj);
			kafkaRequestLog.setCost(timeConsumeMs);

			ResponseBase responseJson = null;
			try {
				responseJson = JSON.parseObject(json, ResponseBase.class);
			} catch (Exception e) {
				LOGGER.error("parse json [" + json + "] exception: ", e);
			}
			if (responseJson != null) {
				kafkaRequestLog.setResultCode(responseJson.getReturnCode());
			}

			KafkaStringProducer.sendMsg(msgTopic, "" + System.currentTimeMillis(), JSON.toJSONString(kafkaRequestLog));
		} catch (Exception e) {
			LOGGER.error("send kafka msg exception:", e);
		}
	}

	private Map<String, String> chkMapDataLength(Map<String, String> dataMap) {
		Map<String, String> ret = new HashMap<String, String>(0);
		if (dataMap != null) {
			for (Iterator<String> iterator = dataMap.keySet().iterator(); iterator.hasNext();) {
				String key = (String) iterator.next();
				String val = dataMap.get(key);
				if (StringUtil.isNotBlank(val)) {
					String newVal = chkStringDataLength(val);
					ret.put(key, newVal);
				} else {
					ret.put(key, val);
				}
			}
		}

		return ret;
	}

	private String chkStringDataLength(String s) {
		if (s == null) {
			return "";
		}
		if (s.length() > 100) {
			return s.substring(0, 99) + "...";
		}
		return s;
	}

	private ResponseDataMapBase doVerify(VerifyDbService verifyDbService, String method, Map<String, String> params, Map<String, String> ctx,
			GatewayIceClientHelper gatewayIceClientHelper) {
		// 如果方法不存在
		MethodInfo methodInfo = verifyDbService.getMethodInfo(method);
		if (methodInfo == null) {
			return new ResponseDataMapBase(GatewayVerifyErrorCodeConstants.ERR_CODE_METHDO_NOT_EXIST_OR_INVALID,
					GatewayVerifyErrorCodeConstants.MAP_ERR_CODE_MSG.get(GatewayVerifyErrorCodeConstants.ERR_CODE_METHDO_NOT_EXIST_OR_INVALID));
		}

		// 判断是否ToB
		if (methodInfo.isTob()) {
			String signType = params.get(CommonConfigConstants.API_PARAM_SIGN_TYPE);
			if (StringUtil.isBlank(signType)) {
				return new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_THRIFT_SERVICE_METHOD_NOT_EXIST,
						"缺少参数：" + CommonConfigConstants.API_PARAM_SIGN_TYPE);
			}

			String timestamp = params.get(CommonConfigConstants.API_PARAM_TIMESTAMP);
			if (StringUtil.isBlank(params.get(CommonConfigConstants.API_PARAM_TIMESTAMP))) {
				return new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_THRIFT_SERVICE_METHOD_NOT_EXIST,
						"缺少参数：" + CommonConfigConstants.API_PARAM_TIMESTAMP);
			}

			String merchantName = params.get(CommonConfigConstants.API_PARAM_MERCHANT_NAME);
			String osapUser = params.get(CommonConfigConstants.API_PARAM_OSAP_USER);

			if (StringUtil.isBlank(merchantName) && StringUtil.isBlank(osapUser)) {
				return new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_THRIFT_SERVICE_METHOD_NOT_EXIST,
						"缺少参数：" + CommonConfigConstants.API_PARAM_MERCHANT_NAME + " 或者 " + CommonConfigConstants.API_PARAM_OSAP_USER);
			}

			String signature = params.get(CommonConfigConstants.API_PARAM_SIGNCODE);
			if (StringUtil.isBlank(signature)) {
				return new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_THRIFT_SERVICE_METHOD_NOT_EXIST,
						"缺少参数：" + CommonConfigConstants.API_PARAM_SIGNCODE);
			}

			String secretKey = "";
			if (StringUtil.isNotBlank(merchantName)) {
				secretKey = verifyDbService.getMerchantSignatureKey(merchantName);
			} else {
				secretKey = verifyDbService.getMerchantSignatureKey(osapUser);
			}

			if (StringUtil.isBlank(secretKey)) {
				return new ResponseDataMapBase(GatewayVerifyErrorCodeConstants.ERR_CODE_MERCHANT_NAME_NOT_EXIST,
						GatewayVerifyErrorCodeConstants.MAP_ERR_CODE_MSG.get(GatewayVerifyErrorCodeConstants.ERR_CODE_MERCHANT_NAME_NOT_EXIST));
			}

			String realMerchantName = StringUtil.isNotBlank(merchantName) ? merchantName : osapUser;
			if (!verifyDbService.chkMerchantApiPermissionValid(realMerchantName, method, ctx.get(CommonConfigConstants.THRIFT_CTX_REMOTE_ADDRESS))) {
				return new ResponseDataMapBase(GatewayVerifyErrorCodeConstants.ERR_CODE_PERMISSION_DENIED,
						GatewayVerifyErrorCodeConstants.MAP_ERR_CODE_MSG.get(GatewayVerifyErrorCodeConstants.ERR_CODE_PERMISSION_DENIED));
			}

			Map<String, String> businessParams = new HashMap<String, String>(0);
			businessParams.putAll(params);
			businessParams.remove(CommonConfigConstants.API_PARAM_SIGN_TYPE);
			businessParams.remove(CommonConfigConstants.API_PARAM_TIMESTAMP);
			businessParams.remove(CommonConfigConstants.API_PARAM_MERCHANT_NAME);
			businessParams.remove(CommonConfigConstants.API_PARAM_OSAP_USER);
			businessParams.remove(CommonConfigConstants.API_PARAM_SIGNCODE);

			String realSignature = "";
			if (StringUtil.isNotBlank(merchantName)) {
				realSignature = SignatureUtil.getSignature(businessParams, merchantName, secretKey, signType, timestamp,
						CommonConfigConstants.API_PARAM_MERCHANT_NAME);
			} else {
				realSignature = SignatureUtil.getSignature(businessParams, osapUser, secretKey, signType, timestamp, CommonConfigConstants.API_PARAM_OSAP_USER);
			}

			if (!signature.equalsIgnoreCase(realSignature)) {
				Set<String> ignoreParams = methodInfo.getSignatureIgnoreParamsSet();
				if (ignoreParams != null) {
					for (Iterator<String> iterator = ignoreParams.iterator(); iterator.hasNext();) {
						String oneParamName = (String) iterator.next();
						businessParams.remove(oneParamName);
					}
				}

				if (StringUtil.isNotBlank(merchantName)) {
					realSignature = SignatureUtil.getSignature(businessParams, merchantName, secretKey, signType, timestamp,
							CommonConfigConstants.API_PARAM_MERCHANT_NAME);
				} else {
					realSignature = SignatureUtil.getSignature(businessParams, osapUser, secretKey, signType, timestamp,
							CommonConfigConstants.API_PARAM_OSAP_USER);
				}

				if (!signature.equalsIgnoreCase(realSignature)) {
					return new ResponseDataMapBase(GatewayVerifyErrorCodeConstants.ERR_CODE_SIGNATURE_ERROR,
							GatewayVerifyErrorCodeConstants.MAP_ERR_CODE_MSG.get(GatewayVerifyErrorCodeConstants.ERR_CODE_SIGNATURE_ERROR));
				}
			}

			ResponseDataMapBase response = new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_SUCCESS);
			return response;
		}

		// 判断是否需要验证票据
		if (methodInfo.isNeedVerifyTicket()) {
			// 设置超时
			int executeTimeoutMs = -1;
			MethodInfo verifyMethod = verifyDbService.getMethodInfo(methodInfo.getVerifyModuleMethod());
			if (verifyMethod != null) {
				executeTimeoutMs = verifyMethod.getTimeoutMs();
			}

			if (executeTimeoutMs <= 0) {
				executeTimeoutMs = DEFAULT_TIMEOUT_MS;
			}

			Map<String, String> verifyParams = new HashMap<String, String>(0);
			verifyParams.putAll(params);
			verifyParams.put(CommonConfigConstants.THRIFT_PARAM_METHOD, methodInfo.getVerifyModuleMethod());
			verifyParams.remove(CommonConfigConstants.THRIFT_PARAM_JSONP_CALLBACK_NAME);
			String verifyJson = invokeThriftService(gatewayIceClientHelper, methodInfo.getVerifyModuleId(), verifyParams, ctx, executeTimeoutMs);
			if (StringUtil.isBlank(verifyJson)) {
				return new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_THRIFT_RESPONSE_NULL,
						"call ice[" + methodInfo.getVerifyModuleMethod() + "] response null");
			}
			try {
				return JSON.parseObject(verifyJson, ResponseDataMapBase.class);
			} catch (Exception e) {
				return new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_PARSE_RESPONSE_ERROR, "解析返回信息[" + verifyJson + "]失败");
			}
		}

		ResponseDataMapBase response = new ResponseDataMapBase(CommonErrorCodeConstants.ERR_CODE_SUCCESS);
		return response;
	}

	private String invokeThriftService(GatewayIceClientHelper gatewayIceClientHelper, String moduleId, Map<String, String> params, Map<String, String> ctx,
			int executeTimeoutMs) {
		final InvokeThriftStatus invokeThriftStatus = new InvokeThriftStatus();
		invokeThriftStatus.setComplete(false);
		invokeThriftStatus.setInvokingMethod(params.get(CommonConfigConstants.THRIFT_PARAM_METHOD));

		// 异步调用
		gatewayIceClientHelper.asyncInvokeThriftService(new AbstractApiJsonCallback() {
			@Override
			public void doResponseApiJson(String response) {
				invokeThriftStatus.setResponseStr(response);
				invokeThriftStatus.setComplete(true);
			}
		}, moduleId, params, ctx, executeTimeoutMs, "gateway");

		// 1000毫秒超时判断
		for (int i = 0; i < executeTimeoutMs; i++) {
			if (invokeThriftStatus.isComplete()) {
				break;
			}
			try {
				Thread.sleep(1);
			} catch (Exception e) {

			}
		}

		if (invokeThriftStatus.isComplete()) {
			return invokeThriftStatus.getResponseStr();
		} else {
			return JSON.toJSONString(new ResponseBase(CommonErrorCodeConstants.ERR_CODE_INVOKE_THRIFT_SERVICE_TIMEOUT,
					"调用Thrift接口[" + invokeThriftStatus.getInvokingMethod() + "]超时"));
		}
	}
}
